#include "new_gpio.h"

#define TAG "GPIO"

static const uint32_t pin_map[] = {
    0x00000001, 0x00000002, 0x00000004, 0x00000008, 0x00000010, 0x00000020, 0x00000040, 0x00000080, //
    0x00000100, 0x00000200, 0x00000400, 0x00000800, 0x00001000, 0x00002000, 0x00004000, 0x00008000, //
    0x00010000, 0x00020000, 0x00040000, 0x00080000, 0x00100000, 0x00200000, 0x00400000, 0x00800000  //
};

// static uint16_t it_pin_mapa[16] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
// static uint16_t it_pin_mapb[16] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};

static const char *gpio_port_str[] = {"A", "B"};
static const char *gpio_mode_str[] = {"IN FLOAT", "IN PULLUP", "IN PULLDOWN", "OUT PUSHPULL_5mA", "OUT PUSHPULL_20mA"};
static const char *it_mode_str[] = {"LOW LEVEL", "HIGH LEVEL", "FALL EDGE", "RISE EDGE"};
static const char *it_status_str[] = {"DISABLED", "ENABLED"};

static uint16_t pin_it_en_mask[] = {0x00000000, 0x00000000};

static gpio_it_cb_t gpio_it_cb = NULL;

void gpio_register_it_cb(gpio_it_cb_t cb)
{
    gpio_it_cb = cb;
}

static uint8_t gpio_get_port_number(gpio_port_t port)
{
    return (port == GPIOA ? 0 : 1);
}

uint8_t gpio_get_pin_number(gpio_pin_t pin)
{
    int8_t cnt = -1;
    while (pin)
    {
        cnt++;
        pin >>= 1;
    }
    return cnt;
}

void gpio_mode_port_pin(gpio_port_t port, gpio_pin_t pin, gpio_mode_t mode)
{
    GPIO_USE_LOG(TAG, "%s%02d | %s", gpio_port_str[gpio_get_port_number(port)], gpio_get_pin_number(pin), gpio_mode_str[mode]);
    switch (mode)
    {
    case GPIO_MODE_IN_FLOAT:
        *(PUINT32V)(port + 20) &= ~pin;
        *(PUINT32V)(port + 16) &= ~pin;
        *(PUINT32V)port &= ~pin;
        break;

    case GPIO_MODE_IN_PULLUP:
        *(PUINT32V)(port + 20) &= ~pin;
        *(PUINT32V)(port + 16) |= pin;
        *(PUINT32V)port &= ~pin;
        break;

    case GPIO_MODE_IN_PULLDOWN:
        *(PUINT32V)(port + 20) |= pin;
        *(PUINT32V)(port + 16) &= ~pin;
        *(PUINT32V)port &= ~pin;
        break;

    case GPIO_MODE_OUT_PUSHPULL_5mA:
        *(PUINT32V)(port + 20) &= ~pin;
        *(PUINT32V)port |= pin;
        break;

    case GPIO_MODE_OUT_PUSHPULL_20mA:
        *(PUINT32V)(port + 20) |= pin;
        *(PUINT32V)port |= pin;
        break;

    default:
        break;
    }
}

void gpio_set_mode(gpio_t pin, gpio_mode_t mode)
{
    gpio_mode_port_pin(pin > 23 ? GPIOB : GPIOA, pin_map[pin > 23 ? pin - 24 : pin], mode);
}

void gpio_set_level(gpio_t pin, gpio_level_t level)
{
    if (level)
    {
        gpio_set_port_high((pin > 23 ? GPIOB : GPIOA), pin_map[pin > 23 ? (pin - 24) : pin]);
    }
    else
    {
        gpio_set_port_low((pin > 23 ? GPIOB : GPIOA), pin_map[pin > 23 ? (pin - 24) : pin]);
    }
}

gpio_level_t gpio_get_level(gpio_t pin)
{
    return gpio_get_port_level((pin > 23 ? GPIOB : GPIOA), pin_map[pin > 23 ? pin - 24 : pin]);
}

void gpio_it_mode_port_pin(gpio_port_t port, gpio_pin_t pin, gpio_it_type_t type)
{
    if ((port == GPIOA && pin > 15) || (port == GPIOB && pin > 15 && pin < 22))
    {
        GPIO_USE_LOG(TAG, "%s%02d | IT NOT SUPPORTED", gpio_port_str[gpio_get_port_number(port)], gpio_get_pin_number(pin));
        return;
    }
    GPIO_USE_LOG(TAG, "%s%02d | IT %s", gpio_port_str[gpio_get_port_number(port)], gpio_get_pin_number(pin), it_mode_str[type]);
    if (port == GPIOA)
    {
        GPIOA_ITModeCfg(pin, (GPIOITModeTpDef)type);
    }
    else
    {
        if (pin == GPIO_Pin_22 || pin == GPIO_Pin_23)
        {
            GPIOPinRemap(ENABLE, RB_PIN_INTX);
        }
        GPIOB_ITModeCfg(pin, (GPIOITModeTpDef)type);
    }
    gpio_it_enable_port_pin(port, pin, ENABLE);
}

void gpio_it_mode(gpio_t pin, gpio_it_type_t type)
{
    gpio_it_mode_port_pin(pin > 23 ? GPIOB : GPIOA, pin_map[pin > 23 ? pin - 24 : pin], type);
}

void gpio_it_enable_port_pin(gpio_port_t port, gpio_pin_t pin, en_state_t en)
{
    if ((port == GPIOA && pin > 15) || (port == GPIOB && pin > 15 && pin < 22))
    {
        GPIO_USE_LOG(TAG, "%s%02d | IT NOT SUPPORTED", gpio_port_str[gpio_get_port_number(port)], gpio_get_pin_number(pin));
        return;
    }
    GPIO_USE_LOG(TAG, "%s%02d | IT %s", gpio_port_str[gpio_get_port_number(port)], gpio_get_pin_number(pin), it_status_str[en]);
    if (port == GPIOA)
    {
        if (en)
        {
            pin_it_en_mask[0] |= pin;
            R16_PA_INT_EN |= pin;
        }
        else
        {
            pin_it_en_mask[0] &= ~pin;
            R16_PA_INT_EN &= ~pin;
        }
        if (pin_it_en_mask[0])
            PFIC_EnableIRQ(GPIO_A_IRQn);
        else
            PFIC_DisableIRQ(GPIO_A_IRQn);
    }
    else
    {
        if (en)
        {
            if (pin == GPIO_Pin_22 || pin == GPIO_Pin_23)
            {
                pin = pin | ((pin & (GPIO_Pin_22 | GPIO_Pin_23)) >> 14);
            }
            pin_it_en_mask[1] |= pin;
            R16_PB_INT_EN |= pin;
        }
        else
        {
            if (pin == GPIO_Pin_22 || pin == GPIO_Pin_23)
            {
                pin = pin | ((pin & (GPIO_Pin_22 | GPIO_Pin_23)) >> 14);
            }
            pin_it_en_mask[1] &= ~pin;
            R16_PB_INT_EN &= ~pin;
        }
        if (pin_it_en_mask[1])
        {
            PFIC_EnableIRQ(GPIO_B_IRQn);
        }
        else
        {
            PFIC_DisableIRQ(GPIO_B_IRQn);
            GPIOPinRemap(DISABLE, RB_PIN_INTX);
        }
    }
}

void gpio_it_enable(gpio_t pin, en_state_t en)
{
    gpio_it_enable_port_pin(pin > 23 ? GPIOB : GPIOA, pin_map[pin > 23 ? pin - 24 : pin], en);
}

__INTERRUPT
__HIGH_CODE
void GPIOA_IRQHandler(void)
{
    if (gpio_it_cb != NULL)
    {
        uint8_t id = gpio_get_pin_number(R16_PA_INT_IF & pin_it_en_mask[0]);
        R16_PA_INT_IF = pin_map[id];
        gpio_it_cb((gpio_t)id);
    }
    else
        R16_PA_INT_IF = R16_PA_INT_IF;
}

__INTERRUPT
__HIGH_CODE
void GPIOB_IRQHandler(void)
{
    if (gpio_it_cb != NULL)
    {
        uint8_t id = gpio_get_pin_number(R16_PB_INT_IF & pin_it_en_mask[1]);
        R16_PB_INT_IF = pin_map[id];
        if ((R16_PIN_ALTERNATE & RB_PIN_INTX) && (id == 8 || id == 9))
            id += 14;
        gpio_it_cb((gpio_t)(id + 24));
    }
    else
        R16_PB_INT_IF = R16_PB_INT_IF;
}
